// 
//
//	File: MaterialRenderer.h
//
// This class defines the material rendering interface.

#ifndef OY_MATERIALRENDERER_H
#define OY_MATERIALRENDERER_H

#include <d3d9.h>
#include "../DataStruct/OyDataStruct.h"
#include "RenderBatch.h"

// ------------------------------------------------------------------------------------------------
// FORWARD DECLARATIONS
// ------------------------------------------------------------------------------------------------
class AbstractMaterial;
class OySkinManager;
class OyDirect3D9;


// material merge modes
enum EMaterialMerge
{
	MM_Add,
	MM_Blend,
	MM_Replace,
};


// MaterialRenderer
class CMaterialRenderer
{
public :
	// ctor(s) & dtor
	CMaterialRenderer();
	~CMaterialRenderer();

	inline static CMaterialRenderer* GetInstance(void) 
	{ 
		static CMaterialRenderer sm_Instance;
		return &sm_Instance;
	}

	// Init & Interface
	inline LPDIRECT3DDEVICE9	GetD3DDevice()		{ return m_pd3dDevice; };
	inline OySkinManager*		GetSkinManager()	{ return m_pSkinManager; }
	inline OyDirect3D9*			GetDirect3D9()		{ return m_pOyD3D9; }


	inline void	Initialize(OySkinManager *pSkinManager, LPDIRECT3DDEVICE9 pDevice, OyDirect3D9 *pOyD3D9)
		{ m_pd3dDevice = pDevice; m_pSkinManager = pSkinManager; m_pOyD3D9 = pOyD3D9; }
	
	// Push/Pop material
	inline void PushMaterial( AbstractMaterial * material, EMaterialMerge mode = MM_Add );
	inline void PopMaterial();

	// Separate Setting Render material according to pass
	void		OnePassPreRenderMaterial( int pass );
	void		OnePassPostRenderMaterial( int pass );
	int			ComputeMaterialPass();		// return how many passes in this material


	// performance recording
	inline void IncreasePropertyNum() { m_PropertyNum++; }
	inline void ClearPropertyNum(){ m_PropertyNum = 0; }
	inline int	GetStackSize() { return materialStack_.Count(); }

	// Render Batch
	void	BeforeDrawRenderBatch();	// Do something before build Render batch lists
	void	AfterDrawRenderBatch();		// Do something after rendering
	void	QueueRenderBatch();			// Queue batch list according value

	void	DoDrawRenderBatch_Opaque();			// Render opaque render batch(opaque+alphatest)
	void	DoDrawRenderBatch_Transparent();	// Render Transparent render batch

	void	PushOpaqueRenderBatch(const COpaqueRenderBatch& batch);
	void	PushAlphaTestRenderBatch(const COpaqueAlphaTestRenderBatch& batch);
	void	PushTransparentRenderBatch(const CTransparentRenderBatch& batch);
public:
	OpaqueRenderBatchVector				m_opaqueBatch;
	OpaqueAlphaTestRenderBatchVector	m_opaqueAlphatestBatch;
	TransparentRenderBatchVector		m_transparentBatch;

private:
	static const int INIT_MATERIALINFO_ARRAY_NUM = 64;
	static const int INIT_SMALL_RENDERBATCH_ARRAY_NUM = 256;
	static const int INIT_LARGE_RENDERBATCH_ARRAY_NUM = 1024;

	// materials & drawing
	inline void Draw( CRenderBatch::BatchType BTtype, AbstractMaterial * material, EMaterialMerge mode = MM_Add );
	
	void Draw( CRenderBatch::BatchType BTtype );
	void FastDraw( CRenderBatch::BatchType BTtype );
	void SlowDraw( CRenderBatch::BatchType BTtype );
	void RenderBatchDrawCall( CRenderBatch::BatchType BTtype );
	

	// private types
	struct MaterialInfo
	{
		inline MaterialInfo( AbstractMaterial * material, EMaterialMerge mode ) : material_( material ), mode_( mode ) {}

		AbstractMaterial * material_;
		EMaterialMerge mode_;
		int firstPass_;
		int lastPass_;
	};

	typedef OyArrayStack<MaterialInfo> MaterialList;
	MaterialList materialStack_;


	// D3D device
	LPDIRECT3DDEVICE9	m_pd3dDevice;
	OySkinManager		*m_pSkinManager;
	OyDirect3D9			*m_pOyD3D9;
	unsigned int		m_PropertyNum;
	int					m_FirstMaterial;
};

// inlines
inline void CMaterialRenderer::PushMaterial( AbstractMaterial * material, EMaterialMerge mode )
{
	if( !materialStack_.Push( MaterialInfo( material, mode ) ) )
	{
		materialStack_.Resize(INIT_MATERIALINFO_ARRAY_NUM + materialStack_.Size());
		materialStack_.Push( MaterialInfo( material, mode ) );
	}
}

inline void CMaterialRenderer::PopMaterial()
{
	materialStack_.Pop();
}

inline void CMaterialRenderer::Draw( CRenderBatch::BatchType BTtype, AbstractMaterial * material, EMaterialMerge mode )
{
	if( !materialStack_.Push( MaterialInfo( material, mode ) ) )
	{
		materialStack_.Resize(INIT_MATERIALINFO_ARRAY_NUM + materialStack_.Size());
		materialStack_.Push( MaterialInfo( material, mode ) );
	}
	Draw( BTtype );
	materialStack_.Pop();
}

#define GetMaterialRenderer() CMaterialRenderer::GetInstance()

#endif
